#include "glare.core.DrawContext.h"
#include "glare.core.ColourValue.h"

#include "Demo.Scene.h"
#include "glare.core.ShaderFactory.h"

#if GLARE_PLATFORM == GLARE_PLATFORM_WIN32
#	include "glare.d3d11.Driver.h"
#endif

#include "glare.gles2.Driver.h"
#include "glare.core.Shader.h"
#include "glare.core.ShaderInputDeclaration.h"
#include "glare.core.GpuBufferDescription.h"
#include "glare.core.GpuResourceFactory.h"
#include "glare.core.TextureDescription.h"
#include "glare.core.Texture.h"

#include "lodepng.h"
#include "glare.core.SamplerState.h"
#include "glare.core.DrawStateFactory.h"
#include "glare.core.ShaderResourceView.h"
#include "Glare.Math.Vector2.h"

#include "hello_texture_png.h"

using namespace Glare;

class HelloTexture : public DemoScene
{
public : struct Vertex
		 {
			 Vector3 Pos;
			 Vector2 Uv;
		 };

public : virtual void Initialize(Driver* driver, PlatformWindow* platform_window) 
		 {
			 _driver = driver;

			 try
			 {
				 _render_window = driver->CreateRenderWindow(platform_window);
				 _render_window->Initialize();
				 _render_window->SetPresentSyncInterval(1);

				 _immediate_context = _driver->GetImmediateDrawContext();

				 _viewport.Left = 0;
				 _viewport.Top = 0;
				 _viewport.Width = (float)_render_window->GetCanvasWidth();
				 _viewport.Height = (float)_render_window->GetCanvasHeight();
				 _viewport.MinDepth = 0;
				 _viewport.MaxDepth = 1;

				 // get the Shader / InputDeclaration factories
				 ShaderFactory* shader_factory = driver->GetShaderFactory();
				 _vertex_shader = shader_factory->CreateVertexShader();
				 _pixel_shader = shader_factory->CreatePixelShader();

				 string errors;
				 // load the vertex & pixel shader
#if GLARE_PLATFORM == GLARE_PLATFORM_WIN32
				 if (driver->GetName() == D3D11Driver::Name)
				 {
					 string shader_structs = 
						 "								\
						 struct Vertex					\
						 {								\
						 float4 Pos : POSITION0;		\
						 float2 Uv : TEXCOORD0;			\
						 };								\
						 struct PS_Input				\
						 {								\
						 float4 Pos : SV_Position;		\
						 float2 Uv : TEXCOORD0;			\
						 };";

					 string vertex_shader = 
						 shader_structs + 
						 "								\
						 PS_Input main(Vertex input)	\
						 {								\
						 PS_Input output;				\
						 output.Pos = input.Pos;		\
						 output.Uv = input.Uv;			\
						 return output;					\
						 }";

					 string pixel_shader = 
						 shader_structs + 
						 "											\
						 Texture2D tex : register(t0);				\
						 SamplerState sam : register(s0);			\
																	\
						 float4 main(PS_Input input) : SV_Target	\
						 {											\
						 return tex.Sample(sam, input.Uv);			\
						 }";

					 _vertex_shader->Compile(vertex_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

					 _pixel_shader->Compile(pixel_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
				 }
#endif
				 if (driver->GetName() == GLES2Driver::Name)
				 {
					 string varying_vars = "varying lowp vec2 Uv;";

					 string vertex_shader = 
						 varying_vars + 
						 "								\
						 attribute vec4 POSITION;		\
						 attribute vec2 TEXCOORD;		\
														\
						 void main(void)				\
						 {								\
						 Uv = TEXCOORD;					\
						 gl_Position = POSITION;		\
						 }";

					 string pixel_shader = 
						 varying_vars + 
						 "										\
						 uniform sampler2D tex;					\
																\
						 void main(void)						\
						 {										\
						 gl_FragColor = texture2D(tex, Uv);		\
						 }";	

					 _vertex_shader->Compile(vertex_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

					 _pixel_shader->Compile(pixel_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
				 }

				 // create an input declaration for the vertex shader
				 _input_decl = shader_factory->CreateInputDeclaration();

				 // add the elements to the input declaration
				 ShaderInputElement position_elem;
				 position_elem.Format = PF_R32G32B32_FLOAT;
				 position_elem.SemanticName = "POSITION";
				 _input_decl->InputElements.push_back(position_elem);

				 ShaderInputElement texcoord_elem;
				 texcoord_elem.Format = PF_R32G32_FLOAT;
				 texcoord_elem.SemanticName = "TEXCOORD";
				 texcoord_elem.Offset = sizeof(Vector3);
				 _input_decl->InputElements.push_back(texcoord_elem);

				 // build the input declaration for the vertex shader
				 _input_decl->Build(_vertex_shader);

				 Glare::uint width, height;
				 std::vector<uchar> image;

				 Glare::uint error = lodepng::decode(image, width, height, hello_texture_png, hello_texture_png_length);

				 if (error)
					 _driver->WriteLog(string("Error while loading PNG file: ") + lodepng_error_text(error), LMT_Error);

				 GpuResourceFactory* resource_factory = driver->GetGpuResourceFactory();

				 const int num_vertices = 4;

				 GpuBufferDescription desc;
				 desc.BindFlags = GpuResourceBind::VertexBuffer;
				 desc.ElementSize = sizeof(Vertex);
				 desc.ElementCount = num_vertices;

				 // create the vertex buffer
				 _vertex_buffer = resource_factory->CreateBuffer(desc);

				 // create a binding for the vertex buffer
				 _vb_binding.Buffer = _vertex_buffer;
				 _vb_binding.Stride = sizeof(Vertex);
				 _vb_binding.Offset = 0;

				 Box dest_box;
				 dest_box.right = sizeof(Vertex) * num_vertices;

				 Vertex vertices[num_vertices] = 
				 {
					 { Vector3(-1, -1, 0),		Vector2::ZERO },	// bottom left
					 { Vector3(-1, 1, 0),		Vector2::ZERO },	// top left
					 { Vector3(1, 1, 0),		Vector2::ZERO },	// top right
					 { Vector3(1, -1, 0),		Vector2::ZERO },	// bottom right
				 };

				 float window_aspect_ratio = _render_window->GetCanvasWidth() / (float)_render_window->GetCanvasHeight();

				 float x_scale;
				 float y_scale;

				 if (window_aspect_ratio < 1)
				 {
					 x_scale = 1;
					 y_scale = 1.0f / window_aspect_ratio;
				 }
				 else
				 {
					 x_scale = window_aspect_ratio;
					 y_scale = 1;
				 }

				 // calculate texture coordinates based on view-space positions
				 for (int i = 0; i < num_vertices; ++i)
				 {
					 vertices[i].Uv.x = (vertices[i].Pos.x * x_scale + 1) * 0.5f;
					 vertices[i].Uv.y = 1.0f - (vertices[i].Pos.y * y_scale + 1) * 0.5f;
				 }

				 // copy the data to the vertex buffer
				 _immediate_context->UpdateGpuResource(_vertex_buffer, 0, &dest_box, vertices, sizeof(Vertex), 0);

				 const int num_indices = 6;

				 desc.BindFlags = GpuResourceBind::IndexBuffer;
				 desc.ElementSize = sizeof(Glare::uint);
				 desc.ElementCount = num_indices;

				 _index_buffer = resource_factory->CreateBuffer(desc);

				 dest_box.right = sizeof(Glare::uint) * num_indices;

				 Glare::uint indices[num_indices] =
				 {
					 0, 1, 2,
					 0, 2, 3
				 };

				 _immediate_context->UpdateGpuResource(_index_buffer, 0, &dest_box, indices, sizeof(Glare::uint), 0);

				 TextureDescription tex_desc;
				 tex_desc.Width = width;
				 tex_desc.Height = height;
				 tex_desc.Format = PF_R8G8B8A8_UNORM;
				 tex_desc.BindFlags = (GpuResourceBind::Flag)(GpuResourceBind::ShaderResource | GpuResourceBind::RenderTarget);

				 _texture = resource_factory->CreateTexture(tex_desc);

				 dest_box.right = width;
				 dest_box.bottom = height;

				 _immediate_context->UpdateGpuResource(_texture, 0, &dest_box, image.data(), 4 * width, 0);

				 DrawStateFactory* state_factory = driver->GetDrawStateFactory();

				 SamplerStateDescription sampler_desc;
				 sampler_desc.Filter = TextureFilter::ANISOTROPIC;
				 _sampler_state = state_factory->CreateSamplerState(sampler_desc);

				 ShaderResourceViewDescription srv_desc;
				 srv_desc.Format = _texture->GetDescription().Format;
				 srv_desc.ViewDimension = ShaderResource::TEXTURE2D;
				 srv_desc.Resource.Texture2D.MipLevels = (Glare::uint)-1;
				 srv_desc.Resource.Texture2D.MostDetailedMip = 0;

				 _texture_shader_view = driver->GetGpuResourceFactory()->CreateShaderResourceView(_texture, srv_desc);
			 }
			 catch (Exception e)
			 {
				 _driver->WriteLog(e.what(), LMT_Error);
			 }
		 }

public : virtual void DrawFrame() 
		 {
			 // set the RenderTarget, the Viewport and perform a clearing operation
			 _immediate_context->OM_SetRenderTarget(_render_window, NULL);
			 _immediate_context->RS_SetViewport(_viewport);
			 _immediate_context->ClearRenderTarget(_render_window, ColourValue::Red);

			 // activate the vertex buffer
			 _immediate_context->IA_SetVertexBuffer(_vb_binding);

			 // activate the index buffer
			 _immediate_context->IA_SetIndexBuffer(_index_buffer, true, 0);

			 // set the vertex & pixel shader
			 _immediate_context->VS_SetVertexShader(_vertex_shader);
			 _immediate_context->PS_SetPixelShader(_pixel_shader);

			 // set the texture view & sampler state
			 _immediate_context->PS_SetSamplerState(0, _sampler_state);
			 _immediate_context->PS_SetShaderResource(0, _texture_shader_view);

			 // set the primitive type & input declaration for the draw call
			 _immediate_context->IA_SetPrimitiveType(GpuPrimitive::TriangleList);
			 _immediate_context->IA_SetInputDeclaration(_input_decl);

			 // submit the draw call
			 _immediate_context->DrawIndexed(6, 0, 0);

			 // swap the GPU buffers
			 _render_window->Present();
		 }

public : virtual void Destroy() 
		 {
			 delete _texture_shader_view;
			 _texture_shader_view = NULL;

			 delete _sampler_state;
			 _sampler_state = NULL;

			 delete _texture;
			 _texture = NULL;

			 delete _index_buffer;
			 _index_buffer = NULL;

			 delete _vertex_buffer;
			 _vertex_buffer = NULL;

			 delete _input_decl;
			 _input_decl = NULL;

			 delete _pixel_shader;
			 _pixel_shader = NULL;

			 delete _vertex_shader;
			 _vertex_shader = NULL;

			 _driver->DestroyRenderWindow(_render_window);
			 _render_window = NULL;
		 }

private : Driver* _driver;
private : RenderWindow* _render_window;
private : DrawContext* _immediate_context;
private : Viewport _viewport;

private : VertexShader* _vertex_shader;
private : PixelShader* _pixel_shader;
private : ShaderInputDeclaration* _input_decl;
private : GpuBuffer* _vertex_buffer;
private : GpuVertexBufferBinding _vb_binding;
private : GpuBuffer* _index_buffer;
private : Texture* _texture;
private : SamplerState* _sampler_state;
private : ShaderResourceView* _texture_shader_view;
};
